﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using PickGold.Data.Hsts.General.Extensions;

namespace PickGold.Data.Hsts.General.Persist
{
	public class StringIndexerPersist : IIndexerPersist<string>
	{
		public const byte VERSION = 40;

		private const int NULL_ID = -1;
		private const double PERCENT = 38.2;

		public void Store(BinaryWriter writer, Func<int, object, string> values, int count, object state)
		{
			writer.Write(VERSION);

			var MAP_CAPACITY = (int)((PERCENT / 100) * count);
			var map = new Dictionary<string, int>(/*MAP_CAPACITY*/); //optimistic variant

			var ID = 0;
			var indexes = new int[count];
			var mode = PersistMode.Dictionary;

			for (int i = 0; i < count; i++)
			{
				var value = values(i, state);
				if (value == null)
				{
					indexes[i] = NULL_ID;
					continue;
				}

				int id;
				if (map.TryGetValue(value, out id))
				{
					indexes[i] = id;
					continue;
				}

				if (map.Count == MAP_CAPACITY)
				{
					mode = PersistMode.Raw;
					break;
				}

				map.Add(value, ID);
				indexes[i] = ID;
				ID++;
			}

			writer.Write((byte)mode);

			switch (mode)
			{
				case PersistMode.Raw:
					{
						new Raw().Store(writer, values, count, state);
					}
					break;

				case PersistMode.Dictionary:
					{
						writer.Write(map.Count);
						foreach (var kv in map.OrderBy(x => x.Value))
							writer.Write(kv.Key);

						new Int32IndexerPersist().Store(writer, (idx, o) => ((int[])o)[idx], count, state);
					}
					break;

				default:
					throw new NotSupportedException(mode.ToString());
			}
		}

		public void Load(BinaryReader reader, Action<int, object, string> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid StringIndexerPersist version.");

			PersistMode mode = (PersistMode)reader.ReadByte();

			switch (mode)
			{
				case PersistMode.Raw:
					{
						new Raw().Load(reader, values, count, state);
					}
					break;

				case PersistMode.Dictionary:
					{
						string[] map = new string[reader.ReadInt32()];
						for (int i = 0; i < map.Length; i++)
							map[i] = reader.ReadString();

						new Int32IndexerPersist().Load(reader, (idx, o, value) => values(idx, o, value == NULL_ID ? null : map[value]), count, state);
					}
					break;

				default:
					throw new NotSupportedException(mode.ToString());
			}
		}

		public class Raw : IIndexerPersist<string>
		{
			public void Store(BinaryWriter writer, Func<int, object, string> values, int count, object state)
			{
				var buffer = new byte[(int)Math.Ceiling(count / 8.0)];

				var array = new string[count];
				var length = 0;

				for (var i = 0; i < count; i++)
				{
					var value = values(i, state);
					if (value != null)
					{
						buffer.SetBit(i, 1);
						array[length++] = value;
					}
					//else
					//    buffer.SetBit(i, 0);
				}

				writer.Write(buffer);

				for (var i = 0; i < length; i++)
					writer.Write(array[i]);
			}

			public void Load(BinaryReader reader, Action<int, object, string> values, int count, object state)
			{
				var buffer = reader.ReadBytes((int)Math.Ceiling(count / 8.0));

				for (var i = 0; i < count; i++)
					values(i, state, buffer.GetBit(i) == 1 ? reader.ReadString() : null);
			}
		}

		private enum PersistMode : byte
		{
			Raw,
			Dictionary
		}
	}
}
